#ifndef _tenacitas__text_log_file__logger_h_
#define _tenacitas__text_log_file__logger_h_

// =============================================================================
/** @file
    Definition of a simple logger */

// =============================================================================
/** todo */

// =============================================================================
//                                 C++ Headers
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>

// =============================================================================
//                                 3rds Headers
#include <boost/shared_ptr.hpp>

// =============================================================================
//                                 Our Headers
#include <tenacitas_log/logger.h>
#include <tenacitas_log/file.h>
#include <tenacitas_exception/exception.h>

// =============================================================================
//                                 Namespaces
using namespace std;


// =============================================================================
//                                 Macro Contants
/** */


// =============================================================================
//                                 Macro Commands
/** */


/** namespace of the organization */
namespace tenacitas {
    /** namespace for the project */
    namespace text_log_file {

        // =========================== Typedefs ===========================

        // ============================ Pre-Declarations ==================

        // ============================ Attributes ========================
        /** */
        static const std::string g_text_log_identifier = "text_line";

        // ============================ Functions =========================
        /** */

        // ============================ Classes ===========================

        /** simple log line header and the log message formater

            This line formater writes in a text file, and the header of the log
            line has this format:
            <tt>level-name|nanoseconds-time-stamp|thread-id|file-name|function-name|line-number|</tt> 

            The log message is written after the log line header, and if it is
            composed by many parts, by using the <tt><<</tt> operator, each part
            will be separated by @p |.

            For example:
            <code>
            log_debug( logger ) << "hello world! " << "it is great to see you!";
            </code>

            will create a line in the log file like this:
            <tt>
            debug|1371041696390809660|141830208|hasher.cpp|md5|345|hello world!|it is great to see you! 
            </tt>

            <b>A word to the programmer user</b>
            You, dear programer, won't need to worry about this class, as it will
            be used directly by tenacitas::log::file class.
        */
        class text_line {

            //    text_line - Friends

            friend std::ostream & operator << ( std::ostream & p_out,
                                                const text_line & p_line ) {
                try {
                    p_out << p_line.m_line;
                }
                catch( std::exception & l_ex ) {
                    std::cerr << "Error at " 
                              << __FILE__ << ", "
                              << __func__ << ", "
                              << __LINE__ << ": "
                              << "could not write to stream" << std::endl;
                }
                return p_out;
            }
            

        public:

            //    text_line - Public Internal Types

            /** Type of pointer to @p text_line */
            typedef boost::shared_ptr<text_line> ptr;

            //    text_line - Public Constructors

            /** */
            ~text_line( ) {
                const std::string l_log_file_name_ended ( m_current_file_name +
                                                          std::string( ".END" ) );

                std::rename( m_current_file_name.c_str( ),
                             l_log_file_name_ended.c_str( ) );
            }
            

            /** Creates a text_line

                @throw */
            static ptr create( ) {
                ptr l_rec;

                try {
                    l_rec = ptr( new text_line( ) );
                }
                catch( std::exception & l_ex ) {
                    std::cerr << "Error at " 
                              << __FILE__ << ", "
                              << __func__ << ", "
                              << __LINE__ << ": "
                              << "could not alocate memory"
                              << std::endl;
                    throw l_ex;
                }
                return l_rec;
            }

            //    text_line - Public Processors

            /** Writes the log line header to a text file

                @param p_file is the log text file handler where the log
                line header will be written
                @param p_file is the file where a new log line should be
                recorded;
                @param p_level is the level of the log message;
                @param p_file_name is the name of the file where the log
                message is being generated;
                @param p_function is the function where the log message is
                being generated;
                @param p_line is the line number in @p p_file_name where the
                log message is being generated
            */
            void new_line( ofstream & p_file,
                           boost::thread::id p_thread_id,
                           level p_level,
                           const char * p_file_name,
                           const char * p_function,
                           int64_t p_line ) const {
                p_file << std::endl << p_level.str( ) << "|"
                       << std::chrono::nanoseconds (
                           std::chrono::high_resolution_clock::now().
                           time_since_epoch( ) ).count( ) 
                       << "|" << th2int( p_thread_id )
                       << "|" << p_file_name
                       << "|" << p_function
                       << "|" << p_line
                       << "|";
                p_file.flush( );
            }

            /** Opens the log file in text mode

                @param p_file is the log text file to be opened
                @param p_file_name is the text log text file name
            */
            void open_file( ofstream & p_file,
                            const std::string & p_file_name ) {
                if( p_file.is_open( ) ) {
                    p_file.close( );

                    const std::string l_log_file_name_ended ( m_current_file_name +
                                                              std::string( ".END" ) );

                    std::rename( m_current_file_name.c_str( ),
                                 l_log_file_name_ended.c_str( ) );
                    
                }

                m_current_file_name = p_file_name;

                p_file.open( p_file_name.c_str( ) );

                p_file << g_text_log_identifier;
            }

            /** Writes data to the file
                Writes any data type that to the file, according to the
                @p t_line_formater class

                @param p_file is the log text file handler where the
                data will be written
                @param  p_data is the data to be written to the file
            */
            template <typename t_data>
            void write ( ofstream & p_file, const t_data & p_data ) const {
                p_file << p_data;
                p_file.flush( );
            }

            /** Reads a line from a existing text log file */
            bool read( ifstream & p_file ) {
                // if ( p_file.eof( ) ) {
                //     return false;
                // }

                std::getline( p_file, m_line );

                if ( ( p_file.eof( ) ) || ( m_line.size( ) == 0 ) ) {
                    return false;
                }

                return true;
            }

            /** */
            void on_thread_end( ofstream & p_file ) {
//                p_file << std::endl;
            }

            /** Informs if a line read is a header line, i.e., contains @p
                g_text_log_identifier */
            bool is_header( ) {
                return ( m_line.find( g_text_log_identifier ) !=
                         std::string::npos );
            }
            
    
            //    text_line - Public Operators

            /** Less-than operator */
            bool operator < ( const text_line & p_stl ) const {
                uint64_t l_this_timestamp;
                uint64_t l_other_timestamp;

                if ( ( get_timestamp( l_this_timestamp ) ) &&
                     ( p_stl.get_timestamp( l_other_timestamp ) ) ) {
                    return ( l_this_timestamp < l_other_timestamp );
                }

                tenacitas::exception::exception l_ex =
                    make_tenacitas_exception("Error comparing timestamps");
                
                throw l_ex;
            }

            /** Equal-to operator */
            bool operator==( const text_line & p_r ) const {
                return ( m_line == p_r.m_line );
            }

            /** Equal-to operator */
            bool operator==( const std::string & p_r ) const {
                return ( m_line == p_r );
            }
            

        private:

            //    text_line - Private Accessors

            /** Retrieves the timestamp from the line read, if possible */
            bool get_timestamp( uint64_t & p_timestamp ) const {
                bool l_rc = false;
                
                size_t l_pos1 = m_line.find('|');
                if ( l_pos1 != std::string::npos ) {
                    size_t l_pos2 = m_line.find( '|', l_pos1 + 1 );
                    if ( l_pos2 != std::string::npos ) {
                        std::string l_timestamp = m_line.substr( l_pos1 + 1,
                                                                 l_pos2 - l_pos1 );
                        p_timestamp = std::stoull( l_timestamp );
                        l_rc = true;
                    }
                }
                return l_rc;
            }

            //    text_line - Private Attributes

            /** Line read */
            std::string m_line;

            /** */
            std::string m_current_file_name;
                
        };

        /** simple policy that decides when log messages should start to be
            recording in a new log file

            The log file name defined by this policy may have one of the formats:

            - While the thread that is logging messages does not end:
            <tt><i>pgm-name</i>_<i>pid</i>_<i>thread-id</i>_<i>file-index</i>.log</tt>

            - After the thread that is logging messages ended:
            <tt><i>pgm-name</i>_<i>pid</i>_<i>thread-id</i>_<i>file-index</i>.log.END</tt>

            Where:
            <tt>pgm-name></tt> is the program name that will generate the log messages
            <tt>pid</tt> is the process identifier of the process that is generating
            the log messages
            <tt>thread-id</tt> is the thread identifier of the thread that is
            generating the log messages
            <tt>file-index</tt> is the index of the log file, where 0 is the first

            For example, if the log files should be created in directory
            <tt>/var/tmp/</tt>, and if two log files were created for a program
            called @p hash, in process @p 3216, in thread @p 7854, we would see:
            <tt>
            /var/tmp/hash_3216_7854_0000000000.log
            /var/tmp/hash_3216_7854_0000000001.log.END
            </tt>

            <b>A word to the programmer user</b>
            You should call static method @p config to define the directory where
            the log files will be created, the program name of the program that
            will generate the log messages, along with the process id, and the
            maximum size of the log file, before a new one is generated.
            That is it! The @p tenacitas::log::file class that will use this
            @p simple_change_file_policy class will call the other necessary
            methods
            @nosubgrouping
        */
        class change_file_policy {

        public:

            //     change_file_policy - Public Internal Types

            /** Pointer to a @p change_file_policy object */
            typedef boost::shared_ptr<change_file_policy> ptr;

            //     change_file_policy - Public Constructors

            /** Defines data for creating every @p change_file_policy
                objects

                @param  p_dir_log is the directory where the log files will be
                created
                @param  p_pgm_name is the name of the program name that will
                generate the log messages
                @param  p_pid is the process identifier of the process that is
                generating the log messages
                @param  p_max_file_size is the maximum size that a log file can
                reach, before a new log file is created
            */
            static void config( const std::string & p_dir_log,
                                const std::string & p_pgm_name,
                                int64_t p_pid,
                                uint64_t p_max_file_size = 1024 * 1014 * 10 );


            /** Destructor */
            ~change_file_policy( );
            

            /** */
            change_file_policy( const change_file_policy & ) =
                delete;

            /** */
            change_file_policy( change_file_policy && ) =
                delete;


            /** Creates a reference to a @p change_file_policy policy

                @param  p_thread_id is the thread identifier that will log
                messages
            */
            static ptr create( boost::thread::id p_thread_id );
            
            //     change_file_policy - Public Processors

            /** Method called when a thread ends

                @param p_file is the stream where the log messages are
                being written
            */
            void on_thread_end( ofstream & p_file );
            
            /** Informs if a new log file should be created

                @param  p_file_size is the current log file size
            */
            bool change_now( uint64_t p_file_size ) const;

            /** Informs the log file name */
            const std::string & file_name( ) {
                return file_name( ++m_file_index );
            }
            

            //     change_file_policy - Public Operators

            /** */
            change_file_policy & operator=( const change_file_policy & ) =
                delete;

            /** */
            change_file_policy & operator=( change_file_policy && ) =
                delete;


        private:

            //     change_file_policy - Private Constructors

            /** Creates a new @p change_file_policy object

                @param  p_thread_id is the thread identifier that will log
                messages
            */
            change_file_policy( boost::thread::id p_thread_id );
            

            //     change_file_policy - Private Processors 

            /** */
            const std::string & file_name( int32_t p_file_index );

            /** */
            std::string final_file_name( ) const;

            //     change_file_policy - Private Attributes

            /** thread identifier that will log messages */
            boost::thread::id m_thread_id;

            /** index of the log file, where 0 is the first */
            int32_t m_file_index;

            /** name of the currente log file */
            std::string m_current_file_name;

            /** log directory where the log files will be created */
            static std::string m_dir_log;

            /** name of the program name that will generate the log messages */
            static std::string m_pgm_name;

            /** process identifier of the process that is generating the log
                messages */
            static int64_t m_pid;

            /** maximum log file size, before it is changed */
            static uint64_t m_max_file_size;
        };

        // =========================== Typedefs ===========================

        /** Type for a text file logger

            This logger specializes @p tenacitas::log::logger with @p
            tenacitas::log::file, itself specialized with @p change_file_policy and
            @p text_line media type, defined above. 

            <b>A word to the programmer user</b>
            You, dear programmer, only need to call the @p config
            to configure the @p tenacitas::text_log_file::logger factory.
            To use the @p  tenacitas::text_log_file::logger factory::logger,
            please read the section <b>A word to the programmer user</b> in the
            tenacitas::log::logger class documentation in
            <tt>cpp/code/inc/tenacitas_log/logger.h</tt> file. 
        */
        typedef tenacitas::log::logger<log::file<change_file_policy, text_line> > logger;

        // ============================ Functions =========================

        /** Configures the @p tenacitas::text_log_file::logger factory

            @param  p_dir_log is the directory where the log files will be
            created
            @param  p_pgm_name is the name of the program name that will
            generate the log messages
            @param  p_pid is the process identifier of the process that is
            generating the log messages
            @param  p_level is the tenacitas::log::level to be used
            for all the @p text_log_file objects created
            @param  p_max_file_size is the maximum size that a log file can
            reach, before a new log file is created

            @attention You can change the tenacitas::log::level for a particular
            @p text_log_file by calling tenacitas::log::logger::set_level
        */
        void config_logger(const std::string & p_dir_log,
                           const std::string & p_pgm_name,
                           std::int64_t p_pid,
                           tenacitas::log::level p_level,
                           uint64_t p_max_file_size = 1024 * 1014 * 10);

    }
}

// =============================================================================
//                     I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::log;


#endif



